﻿using DotNetExtensions.AspNet;
using Habi.Solver.Models.Resources;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Habi.Solver.Common
{
    public class TaskFtpUploader : Singleton<TaskFtpUploader>
    {
        private TaskFtpUploader() { }
        
        private readonly Dictionary<long, ResourceTaskModel> __TASK__ = new Dictionary<long, ResourceTaskModel>();

        private readonly Dictionary<long, CancellationTokenSource> Cancellation = new Dictionary<long, CancellationTokenSource>();

        public event TransferTaskEvent OnTaskCompleted;
        public event TransferTaskEvent OnTaskProgressChanged;

        public string BaseUrl { get; set; }
        public string User { get; set; }
        public string Password { get; set; }

        public void Start(ResourceTaskModel task)
        {
            lock (mutex)
            {
                if (__TASK__.ContainsKey(task.Id)) return;

                __TASK__.Add(task.Id, task);

                HttpRequestStart(task);
            }
        }

        private void HttpRequestStart(ResourceTaskModel task)
        {
            CancellationTokenSource source = new CancellationTokenSource();
            Cancellation.Add(task.Id, source);

            Task.Factory.StartNew((args) =>
            {
                try
                {
                    task.Status = (int)ResourceTaskStatus.Uploading;
                    OnTaskProgressChanged?.Invoke(new TaskEventArgs(task));

                    CancellationToken cancel = (CancellationToken)args;

                    string filename = Path.GetFileName(task.Url);

                    string url = Path.Combine(BaseUrl, filename);
                    var request = (FtpWebRequest)WebRequest.Create(url);
                    request.Method = WebRequestMethods.Ftp.UploadFile;

                    request.UseBinary = true;
                    request.KeepAlive = false;
                    request.UsePassive = false;

                    if (!string.IsNullOrEmpty(User) && !string.IsNullOrEmpty(Password))
                    {
                        request.Credentials = new NetworkCredential(User, Password);
                        request.UsePassive = true;
                    }

                    int chunk = 1 * 1024 * 1024;
                    int content = 0;
                    long size = 0;
                    using (var file = new FileStream(task.Url, FileMode.Open, FileAccess.Read))
                    {
                        request.ContentLength = file.Length;
                        size = file.Length;
                        using (var stream = request.GetRequestStream())
                        {
                            byte[] buffer = new byte[chunk];
                            int real = 0;
                            do
                            {
                                real = file.Read(buffer, 0, buffer.Length);
                                stream.Write(buffer, 0, real);
                                content += real;

                                task.Progress = content / size / 100f;
                                task.Size = size;
                                OnTaskProgressChanged?.Invoke(new TaskEventArgs(task));
                            }
                            while (real > 0);
                        }
                    }

                    if (content >= size)
                    {
                        task.Progress = 1f;
                        task.Size = size;
                        task.Status = (int)ResourceTaskStatus.Uploaded;
                        OnTaskProgressChanged?.Invoke(new TaskEventArgs(task, task.Url, url, true));
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Task download error, msg: {0}", ex.Message);
                    task.Status = (int)ResourceTaskStatus.Error;
                    OnTaskCompleted?.Invoke(new TaskEventArgs(task));
                }

            }, source.Token, source.Token);
        }

        public void Pause(ResourceTaskModel task)
        {
            lock (mutex)
            {
                if (!Cancellation.ContainsKey(task.Id)) return;
                Cancellation[task.Id].Cancel();
            }
        }

        public void Delete(ResourceTaskModel task)
        {
            lock (mutex)
            {
                if (!Cancellation.ContainsKey(task.Id)) return;
                Cancellation[task.Id].Cancel();
                __TASK__.Remove(task.Id);
            }
        }
    }
}
